﻿
#region Copyright & License
// --------------------------
// Copyright 2008 Daniel Gray
// --------------------------
// This file is part of Kale.
//
// Kale is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Kale is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with Kale.  If not, see <http://www.gnu.org/licenses/>.
#endregion
#region File details
// --------------------------------------------------------
// Filename          : ComplexNodeCache.cs
// Author            : Daniel Gray
// Created           : 15/04/2008
// --------------------------------------------------------
#endregion
            
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Caching;
using System.Web;
using Kale.Core.Configuration;
using System.Collections;
using System.IO;
using System.Threading;

namespace Kale.Core
{
   public class ComplexNodeCache : BaseNodeCache
   {
      #region Fields

      private static string _prefix = typeof(ComplexNodeCache).FullName + "_";

      private Cache _cache;
      private DateTime _lastModificationsCheck = DateTime.Now;
      private Thread _modificationsWorkerThread;
      private int _secondsBetweenModificationChecks;
      private CacheExpirationType _expirationType;
      private int _expirationMinutes;
      private CacheItemPriority _priority;

      #endregion

      /// <summary>
      /// Gets the cache object.
      /// </summary>
      /// <returns></returns>
      private static Cache GetCacheObject()
      {
         // try and find an existing cache we can use...
         if (HttpContext.Current != null)
         {
            return HttpContext.Current.Cache;
         }
         else if (HttpRuntime.Cache != null)
         {
            return HttpRuntime.Cache;
         }
         else
         {
            // no runtime we can use - PUKE!
            throw new NoHttpRuntimeException("Use of Complex caching requires the presence of a HttpRuntime.");
         }
      }

      /// <summary>
      /// Initializes a new instance of the <see cref="ComplexNodeCache"/> class.
      /// </summary>
      /// <param name="secondsBetweenModificationChecks">The seconds between modification checks.</param>
      /// <param name="expirationType">Type of the expiration.</param>
      /// <param name="expirationMinutes">The expiration minutes.</param>
      /// <param name="priority">The priority.</param>
      public ComplexNodeCache(int secondsBetweenModificationChecks, CacheExpirationType expirationType, int expirationMinutes, CacheItemPriority priority)
      {
         _cache = GetCacheObject();
         _secondsBetweenModificationChecks = secondsBetweenModificationChecks;
         _expirationType = expirationType;
         _expirationMinutes = expirationMinutes;
         _priority = priority;

         ThreadStart start = new ThreadStart(DoModificationsCheckWork);
         _modificationsWorkerThread = new Thread(start);
         _modificationsWorkerThread.Start();
      }

      private void DoModificationsCheckWork()
      {
         try
         {
            int sleepPeriod = _secondsBetweenModificationChecks * 1000;

            while (true)
            {
               Thread.Sleep(sleepPeriod);
               CheckForModifications();
            }
         }
         catch (ThreadAbortException)
         {

         }
      }

      ~ComplexNodeCache()
      {
         if (_modificationsWorkerThread != null)
         {
            if (_modificationsWorkerThread.ThreadState == ThreadState.Running ||
                _modificationsWorkerThread.ThreadState == ThreadState.Background ||
                _modificationsWorkerThread.ThreadState == ThreadState.Suspended ||
                _modificationsWorkerThread.ThreadState == ThreadState.WaitSleepJoin)
            {
               _modificationsWorkerThread.Abort();
            }
         }
      }


      #region Private properties

      /// <summary>
      /// Gets the cache.
      /// </summary>
      /// <value>The cache.</value>
      private Cache Cache
      {
         get { return _cache; }
      }

#endregion

      /// <summary>
      /// Gets the absolute expiry date time.
      /// </summary>
      /// <value>The absolute expiry date time.</value>
      public DateTime AbsoluteExpiry
      {
         get
         {
            if (_expirationType == CacheExpirationType.Absolute)
            {
               return DateTime.Now.AddMinutes(_expirationMinutes);
            }
            else
            {
               return Cache.NoAbsoluteExpiration;
            }
         }
      }

      /// <summary>
      /// Gets the sliding expiration timespan.
      /// </summary>
      /// <value>The sliding expiration timespan.</value>
      public TimeSpan SlidingExpiration
      {
         get
         {
            if (_expirationType == CacheExpirationType.Sliding)
            {
               return TimeSpan.FromMinutes(_expirationMinutes);
            }
            else
            {
               return Cache.NoSlidingExpiration;
            }
         }
      }

      private void InternalAddNode(IImageNode node)
      {
         Cache.Add(_prefix + node.NodeId.GetCacheKey(),
                    node,
                    null,
                    AbsoluteExpiry,
                    SlidingExpiration,
                    _priority,
                    new CacheItemRemovedCallback(CacheItemRemoved));
      }

      private void CacheItemRemoved(string key, object value, CacheItemRemovedReason reason)
      {
         if (reason == CacheItemRemovedReason.Expired || reason == CacheItemRemovedReason.Underused)
         {
            // the node has changed - so set it stale
            IImageNode node = value as IImageNode;

            if (node != null) node.MakeStale(NodeStaleReason.Old);
         }
      }

      public DateTime LastModificationsCheck
      {
         get { return _lastModificationsCheck; }
      }

      public void CheckForModifications()
      {
         foreach (DictionaryEntry entry in Cache)
         {
            if ((entry.Key as string).StartsWith(_prefix))
            {
               IImageNode node = entry.Value as IImageNode;

               if (node != null)
               {
                  DateTime lastWrite = DateTime.MinValue;

                  if (node.NodeInformation is FileInfo)
                  {
                     lastWrite = File.GetLastWriteTime(node.NodeInformation.FullName);
                  }
                  else
                  {
                     lastWrite = Directory.GetLastWriteTime(node.NodeInformation.FullName);
                  }

                  if (!node.NodeInformation.Exists || lastWrite > _lastModificationsCheck)
                  {
                     // remove from cache
                     Cache.Remove(_prefix + node.NodeId.GetCacheKey());
                     // make stale
                     node.MakeStale(NodeStaleReason.Modified);
                  }
               }
            }
         }

         _lastModificationsCheck = DateTime.Now;
      }

      public override void AddNode(IImageNode node)
      {
         lock (Cache)
         {
            InternalAddNode(node);
         }
      }

      public override void RemoveNode(IImageNode node)
      {
         lock (Cache)
         {
            node.MakeStale(NodeStaleReason.Removed);
            Cache.Remove(_prefix + node.NodeId.GetCacheKey());
         }
      }

      public override void RemoveNode(NodeId id)
      {
         lock (Cache)
         {
            IImageNode node = Cache[_prefix + id.GetCacheKey()] as IImageNode;
            if (node != null)
            {

               node.MakeStale(NodeStaleReason.Removed);
               Cache.Remove(_prefix + id.GetCacheKey());

            }
         }
      }

      public override void Clear()
      {
         lock (Cache)
         {
            List<DictionaryEntry> entries = new List<DictionaryEntry>();

            foreach (DictionaryEntry entry in Cache)
            {
               if ((entry.Key as string).StartsWith(_prefix))
               {
                  entries.Add(entry);
               }
            }

            foreach (DictionaryEntry entry in entries)
            {
               Cache.Remove(entry.Key as string);
            }
         }
      }

      public override bool HasNode(NodeId id)
      {
         lock (Cache)
         {
            return Cache[_prefix + id.GetCacheKey()] != null;
         }
      }

      public override IImageNode GetNode(NodeId id)
      {
         lock (Cache)
         {
            return Cache[_prefix + id.GetCacheKey()] as IImageNode;
         }
      }

      public override int Count
      {
         get
         {
            lock (Cache)
            {
               int count = 0;

               foreach (DictionaryEntry entry in Cache)
               {
                  if ((entry.Key as string).StartsWith(_prefix))
                  {
                     count++;
                  }
               }

               return count;
            }
         }
      }
   }
}
